package com.aim.analysis.records;

import com.aim.analysis.records.C;
import com.aim.analysis.records.Game;
import com.aim.analysis.records.GameRecord;
import com.aim.analysis.records.RecordType;
import com.aim.util.Histogram;
import com.google.common.primitives.Doubles;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

/**
* User: Avraham Shvartzon
* Date: 9/16/14
*/
public class PerformanceHolder {

    public Game game;
    public Histogram scoreHolder = new Histogram(0, 4 * 60 * 1000, 240);
    public Histogram avgHolder = new Histogram(0, 4 * 60, 240);
    public Histogram segmentHolder = new Histogram(0, 4 * 60, 240);

    public PerformanceHolder(Game game) {
        this.game = game;
    }

    public void process(){
        fillScoreHolder();

        Set<GameRecord> records = game.getRecords();
        double[] scores = scoreHolder.getBuckets();

        int currentTimeInSec = 0;
        double prevSegmentNetScore = 18;
        double totalFreezeTime = 0;
        double totalScore = 18;
        double segmentFreezeTime = 0;
        double segmentStartTime = 0;
        double segmentScore = 0;

        for (GameRecord record : records) {
            int timeInSec = getTime(record)/1000;
            if (timeInSec > 0 || record.recordType == RecordType.mechanicalConditionChanged || record.recordType == RecordType.gameEnd){

                // 1. predicting performance of next second.
                double segmentNetScore = prevSegmentNetScore;
                long roundedTime = Math.round((double) record.time / 1000);
                while (currentTimeInSec < roundedTime){
                    double avgNetScore = currentTimeInSec == 0 ? 18 : totalScore / (currentTimeInSec - totalFreezeTime);

                    if (currentTimeInSec - segmentStartTime - segmentFreezeTime > 3){
                        double segmentNetTime = currentTimeInSec - segmentStartTime - segmentFreezeTime;
                        segmentNetScore = segmentScore / segmentNetTime;
                    }

                    totalScore += scores[currentTimeInSec];
                    segmentScore += scores[currentTimeInSec];

                    currentTimeInSec += 1;

                    // add prediction
                    if (currentTimeInSec - roundedTime >=timeInSec || timeInSec == 0){
                        avgHolder.addValue(currentTimeInSec, avgNetScore);
                        segmentHolder.addValue(currentTimeInSec, segmentNetScore);
                    }
                }

                // 2. calculate freeze time.
                totalFreezeTime += timeInSec;
                segmentFreezeTime += timeInSec;

                // 3. calculate segmentParams.
                if (record.recordType == RecordType.maintenaneAction || record.recordType == RecordType.mechanicalConditionChanged){
                    segmentStartTime = roundedTime;
                    segmentFreezeTime = timeInSec;
                    segmentScore = 0;
                    prevSegmentNetScore = segmentNetScore;
                }
            }
        }
    }

    private void fillScoreHolder() {
        for (long collisionTime : game.collisionTimes) {
            scoreHolder.addValue(collisionTime, -Scenario.instance().collisionCost);
        }
        for (long shutdownTime : game.shutdownTimes) {
            scoreHolder.addValue(shutdownTime, Scenario.instance().shutDownScore);
        }
    }

    public double[] processActiveSeconds(){
        fillScoreHolder();
        boolean[] activeSeconds = new boolean[scoreHolder.getBuckets().length];
        Arrays.fill(activeSeconds, true);
        for (GameRecord record : game.getRecords()) {
            int duration = getTime(record) / 1000;
            if (duration > 0){
                int timeInSec = ((record.time + 500) / 1000);
                int endTime = Math.min(timeInSec + duration, activeSeconds.length);
                while (timeInSec < endTime){
                    activeSeconds[timeInSec] = false;
                    timeInSec++;
                }
            }
        }

        List<Double> scoreInActiveSec = new ArrayList<Double>();
        double[] buckets = scoreHolder.getBuckets();
        for (int i = 0; i < buckets.length; i++) {
            if (activeSeconds[i]){
                scoreInActiveSec.add(buckets[i]);
            }
        }
        return Doubles.toArray(scoreInActiveSec);
    }

    private int getTime(GameRecord record) {
        switch (record.recordType) {
            case maintenaneAction:
                return Scenario.instance().maintenanceTime;
            case repairAction:
                int repairTime = 0;
                switch (record.mechanicalCondition) {
                    case M1:
                        repairTime = Scenario.instance().minorTime;
                        break;
                    case M2:
                        repairTime = Scenario.instance().moderateTime;
                        break;
                    case M3:
                        repairTime = Scenario.instance().severeTime;
                        break;
                }
                return repairTime;
            case fuelFillBySpaceship:
                return Scenario.instance().REFUEL_BY_SPACESHIP_TIME;
        }
        return 0;
    }

    public double avgMSE(){
        return MSE(scoreHolder.getBuckets(), avgHolder.getBuckets());
    }

    public double segmentMSE(){
        return MSE(scoreHolder.getBuckets(), segmentHolder.getBuckets());
    }

    private double MSE (double[] a, double[] b){
        double mse = 0;
        for (int i = 1; i < a.length-1; i++) {
            double err = a[i] - b[i];
            mse += err * err;
        }
        return mse / (a.length - 2);
    }
}
